package io.rong.app.adapter;


import android.content.Context;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

import java.util.List;

import io.rong.imkit.widget.adapter.BaseAdapter;

public abstract class CompositeAdapter<T> extends BaseAdapter<T> {

	private static final int ITEM_VIEW_BASE_TYPE = 1;
	private static final String fTag = "CompositeAdapter";

	public static class Partition<T> {
		boolean showIfEmpty;
		boolean hasHeader;

		List<T> list;
		int idColumnIndex;
		int count;

		public Partition(boolean showIfEmpty, boolean hasHeader, List<T> list) {
			this.showIfEmpty = showIfEmpty;
			this.hasHeader = hasHeader;
			this.list = list;
		}

		/**
		 * True if the directory should be shown even if no contacts are found.
		 */
		public boolean getShowIfEmpty() {
			return showIfEmpty;
		}

		public boolean getHasHeader() {
			return hasHeader;
		}

		public List<T> getList() {
			return list;
		}
	}

	public final Context mContext;
	private Partition<T>[] mPartitions;
	private int mSize = 0;
	private int mCount = 0;
	private boolean mCacheValid = true;
	private boolean mNotificationsEnabled = true;
	private boolean mNotificationNeeded;

	public CompositeAdapter(Context context) {
		mContext = context;
	}

	public Context getContext() {
		return mContext;
	}

	/**
	 * Registers a partition. The cursor for that partition can be set later.
	 * Partitions should be added in the order they are supposed to appear in
	 * the list.
	 */
	public void addPartition(boolean showIfEmpty, boolean hasHeader, List<T> list, int size) {
		addPartition(new Partition<T>(showIfEmpty, hasHeader, list), size);
	}

	@SuppressWarnings("unchecked")
	public void addPartition(Partition<T> partition, int size) {
		if (mPartitions == null || mPartitions.length != size) {

			mPartitions = new Partition[size];
			mSize = 0;
		}
		mPartitions[mSize++] = partition;
//        Log.e(fTag, "create Partition -------------- mPartitions = " + mPartitions);
		invalidate();
		notifyDataSetChanged();
	}

	protected Partition<T>[] getPartitions() {
		return mPartitions;
	}

	public void removePartition(int partitionIndex) {

		System.arraycopy(mPartitions, partitionIndex + 1, mPartitions, partitionIndex, mSize - partitionIndex - 1);
		mSize--;
		invalidate();
		notifyDataSetChanged();
	}

	/**
	 * Removes cursors for all partitions.
	 */
	public void clearPartitions() {
		if (mSize == 0) {
			return;
		}
		for (int i = 0; i < mSize; i++) {
			List<T> list = mPartitions[i].list;
			if (list != null) {
				list.clear();
				mPartitions[i].list = list = null;
			}
		}
		mSize = 0;
		invalidate();
		notifyDataSetChanged();
	}

	public void setHasHeader(int partitionIndex, boolean flag) {
		mPartitions[partitionIndex].hasHeader = flag;
		invalidate();
	}

	public void setShowIfEmpty(int partitionIndex, boolean flag) {
		mPartitions[partitionIndex].showIfEmpty = flag;
		invalidate();
	}

	public Partition<T> getPartition(int partitionIndex) {
		if (partitionIndex >= mSize) {
			throw new ArrayIndexOutOfBoundsException(partitionIndex);
		}
		return mPartitions[partitionIndex];
	}

	protected void invalidate() {
		mCacheValid = false;
	}

	public int getPartitionCount() {
		return mSize;
	}

	protected void ensureCacheValid() {
		if (mCacheValid) {
			return;
		}

		mCount = 0;
		for (int i = 0; i < mSize; i++) {

			int count = mPartitions[i].list != null ? mPartitions[i].list.size() : 0;
			if (mPartitions[i].hasHeader) {
				if (count != 0 || mPartitions[i].showIfEmpty) {
					count++;
				}
			}
			mPartitions[i].count = count;
			mCount += count;
		}

		mCacheValid = true;
	}

	/**
	 * Returns true if the specified partition was configured to have a header.
	 */
	public boolean hasHeader(int partition) {
		return mPartitions[partition].hasHeader;
	}

	/**
	 * Returns the total number of list items in all partitions.
	 */
	public int getCount() {
		ensureCacheValid();
		return mCount;
	}

	/**
	 * Returns the cursor for the given partition
	 */
	public List<T> getData(int partition) {
		return mPartitions[partition].list;
	}

	/**
	 * Changes the cursor for an individual partition.
	 */
	public void changeCursor(int partition, List<T> data) {
		mPartitions[partition].list = data;

		invalidate();
		notifyDataSetChanged();
	}

	/**
	 * Returns true if the specified partition has no cursor or an empty cursor.
	 */
	public boolean isPartitionEmpty(int partition) {
		return mPartitions[partition].list == null || mPartitions[partition].list.size() == 0;
	}

	/**
	 * Given a list position, returns the index of the corresponding partition.
	 */
	public int getPartitionForPosition(int position) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				return i;
			}
			start = end;
		}
		return -1;
	}

	/**
	 * Given a list position, return the offset of the corresponding item in its
	 * partition. The header, if any, will have offset -1.
	 */
	public int getOffsetInPartition(int position) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				int offset = position - start;
				if (mPartitions[i].hasHeader) {
					offset--;
				}
				return offset;
			}
			start = end;
		}
		return -1;
	}

	/**
	 * Returns the first list position for the specified partition.
	 */
	public int getPositionForPartition(int partition) {
		ensureCacheValid();
		int position = 0;
		for (int i = 0; i < partition; i++) {
			position += mPartitions[i].count;
		}
		return position;
	}

	@Override
	public int getViewTypeCount() {
		return getItemViewTypeCount() + 1;
	}

	/**
	 * Returns the overall number of item view types across all partitions. An
	 * implementation of this method needs to ensure that the returned count is
	 * consistent with the values returned by {@link #getItemViewType(int, int)}
	 * .
	 */
	public int getItemViewTypeCount() {
		return 2;
	}

	/**
	 * Returns the view type for the list item at the specified position in the
	 * specified partition.
	 */
	protected int getItemViewType(int partition, int position) {
		return ITEM_VIEW_BASE_TYPE;
	}

	@Override
	public int getItemViewType(int position) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				int offset = position - start;
				if (mPartitions[i].hasHeader && offset == 0) {
					// return IGNORE_ITEM_VIEW_TYPE;
					return ITEM_VIEW_BASE_TYPE + 1;
				}
				return getItemViewType(i, position);
			}
			start = end;
		}

		throw new ArrayIndexOutOfBoundsException(position);
	}

	public View getView(int position, View convertView, ViewGroup parent) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				int offset = position - start;
				if (mPartitions[i].hasHeader) {
					offset--;
				}
				View view;
				if (offset == -1) {
					view = getHeaderView(i, mPartitions[i].list, convertView, parent);
				} else {

					view = getView(i, mPartitions[i].list, offset, convertView, parent);
				}
				if (view == null) {
					throw new NullPointerException("View should not be null, partition: " + i + " position: " + offset);
				}
				return view;
			}
			start = end;
		}

		throw new ArrayIndexOutOfBoundsException(position);
	}

	/**
	 * Returns the header view for the specified partition, creating one if
	 * needed.
	 */
	protected View getHeaderView(int partition, List<T> data, View convertView, ViewGroup parent) {
		View view = convertView != null ? convertView : newHeaderView(mContext, partition, data, parent);
		bindHeaderView(view, partition, data);
		return view;
	}

	/**
	 * Creates the header view for the specified partition.
	 */
	protected View newHeaderView(Context context, int partition, List<T> data, ViewGroup parent) {
		return null;
	}

	/**
	 * Binds the header view for the specified partition.
	 */
	protected void bindHeaderView(View view, int partition, List<T> data) {
	}

	/**
	 * Returns an item view for the specified partition, creating one if needed.
	 */
	protected View getView(int partition, List<T> data, int position, View convertView, ViewGroup parent) {
		View view;
		if (convertView != null) {
			view = convertView;
		} else {
			view = newView(mContext, partition, data, position, parent);
		}
		bindView(view, partition, data, position);
		return view;
	}

	/**
	 * Creates an item view for the specified partition and position. Position
	 * corresponds directly to the current cursor position.
	 */
	protected abstract View newView(Context context, int partition, List<T> data, int position, ViewGroup parent);

	/**
	 * Binds an item view for the specified partition and position. Position
	 * corresponds directly to the current cursor position.
	 */
	protected abstract void bindView(View v, int partition, List<T> data, int position);

	/**
	 * Returns a pre-positioned cursor for the specified list position.
	 */
	public T getItem(int position) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				int offset = position - start;
				if (mPartitions[i].hasHeader) {
					offset--;
				}
				if (offset == -1) {
					return null;
				}

				return mPartitions[i].list.get(offset);
			}
			start = end;
		}

		return null;
	}

	/**
	 * Returns the item ID for the specified list position.
	 */
	public long getItemId(int position) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				int offset = position - start;
				if (mPartitions[i].hasHeader) {
					offset--;
				}
				if (offset == -1) {
					return 0;
				}
				if (mPartitions[i].idColumnIndex == -1) {
					return 0;
				}

				if (mPartitions[i].list == null || mPartitions[i].list.size() <= 0) {
					return 0;
				}
				return position;
			}
			start = end;
		}

		return 0;
	}

	/**
	 * Returns false if any partition has a header.
	 */
	@Override
	public boolean areAllItemsEnabled() {
		for (int i = 0; i < mSize; i++) {
			if (mPartitions[i].hasHeader) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Returns true for all items except headers.
	 */
	@Override
	public boolean isEnabled(int position) {
		ensureCacheValid();
		int start = 0;
		for (int i = 0; i < mSize; i++) {
			int end = start + mPartitions[i].count;
			if (position >= start && position < end) {
				int offset = position - start;
				if (mPartitions[i].hasHeader && offset == 0) {
					return false;
				} else {
					return isEnabled(i, offset);
				}
			}
			start = end;
		}

		return false;
	}

	/**
	 * Returns true if the item at the specified offset of the specified
	 * partition is selectable and clickable.
	 */
	protected boolean isEnabled(int partition, int position) {
		return true;
	}

	/**
	 * Enable or disable data change notifications. It may be a good idea to
	 * disable notifications before making changes to several partitions at
	 * once.
	 */
	public void setNotificationsEnabled(boolean flag) {
		mNotificationsEnabled = flag;
		if (flag && mNotificationNeeded) {
			notifyDataSetChanged();
		}
	}

	@Override
	public void notifyDataSetChanged() {
		if (mNotificationsEnabled) {
			mNotificationNeeded = false;
			super.notifyDataSetChanged();
		} else {
			mNotificationNeeded = true;
		}
	}

}
